/*
	QUBE
	Sandra Obermann - QPT 1 - 2012
	fhs33730 - 1110601021
	FH Salzburg - Studiengang MultiMediaTechnolgy
*/

// Cinder Includes
#include "cinder/app/AppBasic.h"

// Own written Includes
#include "../include/QUBE.h"
#include "../include/DataManager.h"

// Used namespaces
using namespace ci;
using namespace ci::gl;

// Initialize QUBE with default parameters
QUBE::QUBE( int qubeSize  ) :
	m_MoveQube( false ),
	m_SlideDown( false ),
	m_QubeSize ( qubeSize ),
	m_ToRotationAngle( 90.0f ),
	m_CurrentQubeColorIndex( 0 ),
	m_CurrentQubeRotationAngle( 0.0f ),
	m_CurrentQubeColorTimer( 0.0f ),
	m_QubePosition( ci::Vec3f(15, E_LEVEL_2, 20) ),
	m_QubeInputDirection( ci::Vec3f::zero() )
{

}

// Destructor for QUBE element
QUBE::~QUBE()
{

}

// Initialize DataManager one single time - when game starts
// QUBE get its changing colors from the DataManager
void QUBE::setup()
{
	m_DataManager = DataManager::getInstance();

	m_CurrentQubeColor = m_DataManager->getQubeColor( m_CurrentQubeColorIndex );
}

// Updates the movement and drawing from the QUBE
void QUBE::update( float deltaTime )
{
	// Update Movement
	if ( m_MoveQube )
	{
		// Checking for movement on same level
		if ( math<float>::abs( m_CurrentQubeRotationAngle ) < math<float>::abs( m_ToRotationAngle ) )
		{
			// let the QUBE fall over to the chosen input direction ( left, right, forward, backward ) with lerp 
			m_CurrentQubeRotationAngle = lerp<float, float>( m_CurrentQubeRotationAngle, m_ToRotationAngle + ( 10.0f * math<float>::signum(m_CurrentQubeRotationAngle) ), deltaTime * 8.0f );
		}
		// Checking for movement up- or downside
		else
		{
			Vec3f tempDirection = m_QubeInputDirection;
			if ( m_SlideDown )
			{
				// Make sure the QUBE only goes up
				m_QubeInputDirection.x = 0.0f;
				m_QubeInputDirection.z = 0.0f;
			}

			m_QubePosition += m_QubeInputDirection * m_QubeSize;

			// Trying move wall up and slide down again
			if ( m_SlideDown && tempDirection.y > 0.0f )
			{
				m_CurrentQubeRotationAngle = 0.0f;
				m_ToRotationAngle = -90.0f;
				m_QubeInputDirection = tempDirection;
				m_QubeInputDirection.y *= -1;
			}
			// otherwise reset move bool variables and rotation angle
			else
			{
				m_MoveQube = false;
				m_SlideDown = false;
				m_CurrentQubeRotationAngle = 0.0f;
			}
		}
	}

	// Update QUBE Color
	updateColor( deltaTime );
}

// Draw QUBE with given parameters and input direction
void QUBE::draw() const
{
	gl::pushMatrices();
		
		// for the movement on the same level ( left / right / forward / backward )
		// move pivot point half QUBE size down and half QUBE size in the chosen direction to rotate around the shifted pivot point
		if ( m_QubeInputDirection.y <= 0 )
		{
			gl::translate( m_QubePosition );
			glTranslatef( m_QubeInputDirection.x * (m_QubeSize * 0.5f), -m_QubeSize * 0.5f, m_QubeInputDirection.z * (m_QubeSize * 0.5f) );
			glRotatef( m_CurrentQubeRotationAngle, m_QubeInputDirection.z, 0, -m_QubeInputDirection.x );
			glTranslatef( -m_QubeInputDirection.x * (m_QubeSize * 0.5f), m_QubeSize * 0.5f, -m_QubeInputDirection.z * (m_QubeSize * 0.5f) );
			gl::translate( -m_QubePosition );
		}
		// for the movement not on same level ( up / down )
		// otherwise move pivot point half QUBE size up and half QUBE size in the chosen direction to rotate around the shifted pivot point
		else
		{
			gl::translate( m_QubePosition );
			glTranslatef( m_QubeInputDirection.x * (m_QubeSize * 0.5f), m_QubeSize * 0.5f, m_QubeInputDirection.z * (m_QubeSize * 0.5f) );
			glRotatef( m_CurrentQubeRotationAngle, m_QubeInputDirection.z, 0, -m_QubeInputDirection.x );
			glTranslatef( -m_QubeInputDirection.x * (m_QubeSize * 0.5f), -m_QubeSize * 0.5f, -m_QubeInputDirection.z * (m_QubeSize * 0.5f) );
			gl::translate( -m_QubePosition );
		}

		// add QUBE color and draw QUBE with fall over animation to the new position
		gl::color( m_CurrentQubeColor );
		drawCube( m_QubePosition, Vec3f((float)m_QubeSize, (float)m_QubeSize, (float)m_QubeSize) );
	gl::popMatrices();
}


// Collision dedection in movement layer
void QUBE::moveToDir( QUBE_MOVEMENT dir)
{
	// update qube
	if(getMoveQube() == true)
		return;

	// Check current QUBE position field to check if there is an obstacle in front/below/sides of the QUBE
	Vec3<int> checkObstacle = getQubePosition();
	checkObstacle /= GRID_SIZE;

	if(E_QUBE_MOVE_UP == dir)
	{
		checkObstacle.x = checkObstacle.x + 1;
	}
	else if(E_QUBE_MOVE_DOWN == dir)
	{
		checkObstacle.x = checkObstacle.x - 1;
	}
	else if(E_QUBE_MOVE_LEFT == dir)
	{
		checkObstacle.z = checkObstacle.z - 1;
	}
	else if(E_QUBE_MOVE_RIGHT == dir)
	{
		checkObstacle.z = checkObstacle.z + 1;
	}

	// Initialize different obstacle cases ( wall, obstacle, same level and dead zone )
	BLOCK* obstacle = m_DataManager->getLevelBlock(checkObstacle.x,checkObstacle.z,checkObstacle.y);
	BLOCK* wall = m_DataManager->getLevelBlock(checkObstacle.x,checkObstacle.z,checkObstacle.y + 1);
	BLOCK* sameLevel = m_DataManager->getLevelBlock(checkObstacle.x,checkObstacle.z,checkObstacle.y - 1);
	BLOCK* deadZone = m_DataManager->getLevelBlock(checkObstacle.x,checkObstacle.z,checkObstacle.y - 2);

	if ( obstacle )
	{
		// if there is a wall then "qube" up and slide down again
		if( wall )
		{
			setMoveQube();
			if(E_QUBE_MOVE_UP == dir)
			{
				setInputDirection( Vec3f( 1.0f, 1.0f, 0.0f ) );
				setToRotationAngle( 90.0f );
			}
			else if(E_QUBE_MOVE_DOWN == dir)
			{
				setInputDirection( Vec3f( -1.0f, 1.0f, 0.0f ) );
				setToRotationAngle( 90.0f );
			}
			else if(E_QUBE_MOVE_LEFT == dir)
			{	
				setInputDirection( Vec3f( 0.0f, 1.0f, -1.0f ) );
				setToRotationAngle( 90.0f );
			}
			else if(E_QUBE_MOVE_RIGHT == dir)
			{	
				setInputDirection( Vec3f( 0.0f, 1.0f, 1.0f ) );
				setToRotationAngle( 90.0f );
			}
			setSlideDown();
		}
		// otherwise if there is a single obstacle "qube" up
		else
		{
			setMoveQube();
			if(E_QUBE_MOVE_UP == dir)
			{
				setInputDirection( Vec3f( 1.0f, 1.0f, 0.0f ) );
				setToRotationAngle( 180.0f );
			}
			else if(E_QUBE_MOVE_DOWN == dir)
			{	
				setInputDirection( Vec3f( -1.0f, 1.0f, 0.0f ) );
				setToRotationAngle( 180.0f );
			}
			else if(E_QUBE_MOVE_LEFT == dir)
			{	
				setInputDirection( Vec3f( 0.0f, 1.0f, -1.0f ) );
				setToRotationAngle( 180.0f );
			}
			else if(E_QUBE_MOVE_RIGHT == dir)
			{	
				setInputDirection( Vec3f( 0.0f, 1.0f, 1.0f ) );
				setToRotationAngle( 180.0f );
			}
		}
	}
	// otherwise check if there is a BLOCK to "qube" on
	else if ( !sameLevel ) 
	{
		// if there is no BLOCK to "qube" on then die
		if( !deadZone || deadZone == (BLOCK*)-1 )
		{
			setMoveQube();
			if(E_QUBE_MOVE_UP == dir)
			{
				setInputDirection( Vec3f( 1.0f, -15.0f, 0.0f ) );
				setToRotationAngle( 90.0f );
			}
			else if(E_QUBE_MOVE_DOWN == dir)
			{
				setInputDirection( Vec3f( -1.0f, -15.0f, 0.0f ) );
				setToRotationAngle( 90.0f );
			}
			else if(E_QUBE_MOVE_LEFT == dir)
			{		
				setInputDirection( Vec3f( 0.0f, -15.0f, -1.0f ) );
				setToRotationAngle( 90.0f ); 
			}
			else if(E_QUBE_MOVE_RIGHT == dir)
			{	
				setInputDirection( Vec3f( 0.0f, -15.0f, 1.0f ) );
				setToRotationAngle( 90.0f );
			}
			m_DataManager->setGameOver(true);
		}
		// otherwise "qube" on the BLOCK upside
		else
		{
			setMoveQube();
			if(E_QUBE_MOVE_UP == dir)
			{
				setInputDirection( Vec3f( 1.0f, -1.0f, 0.0f ) );
				setToRotationAngle( 180.0f );
			}
			else if(E_QUBE_MOVE_DOWN == dir)
			{
				setInputDirection( Vec3f( -1.0f, -1.0f, 0.0f ) );
				setToRotationAngle( 180.0f );
			}
			else if(E_QUBE_MOVE_LEFT == dir)
			{	
				setInputDirection( Vec3f( 0.0f, -1.0f, -1.0f ) );
				setToRotationAngle( 180.0f );
			}
			else if(E_QUBE_MOVE_RIGHT == dir)
			{	
				setInputDirection( Vec3f( 0.0f, -1.0f, 1.0f ) );
				setToRotationAngle( 180.0f );
			}
		}
	}
	else
	{
		if ( !getMoveQube() )
		{
			setMoveQube();
			if(E_QUBE_MOVE_UP == dir)
			{
				// or simply fall over forward
				setInputDirection( Vec3f( 1.0f, 0.0f, 0.0f ) );
				setToRotationAngle( 90.0f );
			}
			else if(E_QUBE_MOVE_DOWN == dir)
			{
				// or simply fall over backward
				setInputDirection( Vec3f( -1.0f, 0.0f, 0.0f ) );
				setToRotationAngle( 90.0f );
			}
			else if(E_QUBE_MOVE_LEFT == dir)
			{		
				// or simply fall over left
				setInputDirection( Vec3f( 0.0f, 0.0f, -1.0f ) );
				setToRotationAngle( 90.0f );
			}
			else if(E_QUBE_MOVE_RIGHT == dir)
			{	
				// or simply fall over right
				setInputDirection( Vec3f( 0.0f, 0.0f, 1.0f ) );
				setToRotationAngle( 90.0f );
			}
		}
	}
}

// Set trigger for moving QUBE
void QUBE::setMoveQube( bool moveQube )
{
	m_MoveQube = moveQube;
}

// Set trigger for sliding QUBE
void QUBE::setSlideDown( bool slideSown )
{
	m_SlideDown = slideSown;
}

// Set input direction for the QUBE to move ( left / right / forward / backward / up / down )
void QUBE::setInputDirection( ci::Vec3f inputDirection )
{
	m_QubeInputDirection = inputDirection;
}

// Returns if the QUBE is moving right now
bool QUBE::getMoveQube() const
{
	return m_MoveQube;
}

// Returns current QUBE position
ci::Vec3f QUBE::getQubePosition() const
{
	return m_QubePosition;
}

// Sets QUBE position
void QUBE::setQubePosition( ci::Vec3f position )
{
	m_QubePosition = position;
}

// Update QUBE color every few seconds
void QUBE::updateColor( float deltaTime )
{
	/*
		add deltaTime ( elapsed time from frame to frame) to m_CurrentQubeColorTimer
		when m_CurrentQubeColorTimer > getMaxTimeToChangeQubeColor (current limit is 1.5f)
		then change to next color position in the color array
	*/

	m_CurrentQubeColorTimer += deltaTime;

	// reset m_CurrentQubeColorTimer and change to next color
	if ( m_CurrentQubeColorTimer > m_DataManager->getMaxTimeToChangeQubeColor() )
	{
		m_CurrentQubeColorTimer = 0.0f;
		m_CurrentQubeColorIndex++;

		// when last color in array is reached begin from first color
		if ( m_CurrentQubeColorIndex > m_DataManager->getQubeColorsSize() - 1 )
		{
			m_CurrentQubeColorIndex = 0;
		}
	}

	// set current color and next color for the lerp to change smooth to the next color
	Color toColor = m_DataManager->getQubeColor( m_CurrentQubeColorIndex );
	m_CurrentQubeColor = lerp<Color, float>( m_CurrentQubeColor, toColor, deltaTime * 6.0f );
}

// Sets the rotation angle for the current move
void QUBE::setToRotationAngle( float rotationAngle )
{
	m_ToRotationAngle = rotationAngle;
}